#include "lexer.h"

#include <cctype>
#include <cstdio>
#include <iostream>
#include <ostream>
#include <string>
#include <unordered_map>
#include <vector>

#include "ast.h"
#include "token.h"

using namespace Utils;
using namespace Token;

bool IsOct(int c) { return '0' <= c && c < '8'; }

bool IsHex(int c) {
    if (isupper(c)) c |= 32;
    return isdigit(c) || 'a' <= c && c < 'g';
}

bool IsValidIdentifier(int c) { return isdigit(c) || isalpha(c) || c == '_'; }

int ToDigit(int c) {
    if (islower(c)) return c - 'a' + 10;
    if (isupper(c)) return c - 'A' + 10;
    return c - '0';
}

bool Lexer::Empty() const { return it == text.end(); }
int Lexer::Peek() const { return Empty() ? -1 : *it; }
void Lexer::Prev() {
    if (it == text.begin()) return;
    --it, --loc.column;
    if (*it == '\n') --loc.line;
}
int Lexer::Next() {
    if (Empty()) return -1;
    if (*it == '\n') ++loc.line, loc.column = 0;
    return ++loc.column, *it++;
}
bool Lexer::Try(char c) { return Peek() == c && (Next(), true); }
bool Lexer::Expect(char c) {
    if (Peek() == c) return Next(), true;
    ErrAt(loc, "expected '%c' but got '%c'", c, Peek());
    return false;
}

void Lexer::SkipSpace() {
    while (!Empty() && isspace(Peek())) Next();
}
void Lexer::SkipInlineComment() {
    while (!Empty())
        if (Next() == '\n') break;
}
void Lexer::SkipBlockComment() {
    while (!Empty())
        if (Next() == '*' && Try('/')) break;
}

BaseToken* Lexer::HandleNumber(Location beginLoc) {
    std::string str;
    while (IsValidIdentifier(Peek()) || Peek() == '.') str += tolower(Next());
    if (str.back() == 'e' || str.back() == 'p') {
        if (Peek() == '+' || Peek() == '-') str += Next();
        while (isdigit(Peek())) str += Next();
    }
    // std::cout << str << std::endl;
    if (str.find('.') != str.npos || str.find('e') != str.npos ||
        str.find('p') != str.npos) {
        float fval;
        sscanf(str.c_str(), "%f", &fval);
        return new Literal(beginLoc, ArithValue(fval));
    } else {
        int ival;
        if (str.size() > 2 && str.substr(0, 2) == "0x")
            sscanf(str.c_str(), "%x", &ival);
        else if (str.size() > 1 && str[0] == '0')
            sscanf(str.c_str(), "%o", &ival);
        else
            sscanf(str.c_str(), "%d", &ival);
        return new Literal(beginLoc, ArithValue(ival));
    }
}

BaseToken* Lexer::HandleIdentifier(Location beginLoc) {
    static std::unordered_map<std::string, Tag> mp{
        {"if", Tag::If},
        {"else", Tag::Else},
        {"while", Tag::While},
        {"break", Tag::Break},
        {"continue", Tag::Continue},
        {"return", Tag::Return},
        {"const", Tag::ConstQualifier},
        {"void", Tag::VoidType},
        {"int", Tag::IntType},
        {"float", Tag::FloatType}};
    std::string value;
    while (IsValidIdentifier(Peek())) value += (char)Next();
    if (!mp.count(value)) {
        return new Identifier(beginLoc, value);
    }
    Tag tag = mp[value];
    return Keyword::MakeKeyword(beginLoc, mp[value]);
}

BaseToken* Lexer::HandleString(Location beginLoc) {
    std::string str;
    while (!Empty() && !Try('"')) {
        if (!Try('\\')) {
            str += Next();
            continue;
        }
        switch (Next()) {
            case 'n':
                str += '\n';
                break;
            case '\\':
                str += '\\';
                break;
        }
    }
    return new String(beginLoc, str);
}

BaseToken* Lexer::NextToken() {
    SkipSpace();
    Location beginLoc = loc;
    auto NewOperator = [&](Tag tag) { return new Operator(beginLoc, tag); };

    char c = (char)Next();
    if (std::string("()[]{},;+-*%").find(c) != std::string::npos) {
        return NewOperator((Tag)c);
    }
    switch (c) {
        case '=':
            if (Try('=')) return NewOperator(Tag::Equal);
            return NewOperator(Tag::Assign);
        case '!':
            if (Try('=')) return NewOperator(Tag::NotEqual);
            return NewOperator(Tag::Not);
        case '<':
            if (Try('=')) return NewOperator(Tag::LessEqual);
            return NewOperator(Tag::Less);
        case '>':
            if (Try('=')) return NewOperator(Tag::GreaterEqual);
            return NewOperator(Tag::Greater);
        case '&':
            Expect('&');
            return NewOperator(Tag::LogicAnd);
        case '|':
            Expect('|');
            return NewOperator(Tag::LogicOr);
        case '/':
            if (Try('/')) return SkipInlineComment(), NextToken();
            if (Try('*')) return SkipBlockComment(), NextToken();
            return NewOperator(Tag::Div);

        case '0' ... '9':
        case '.':
            return Prev(), HandleNumber(beginLoc);
        case '_':
        case 'A' ... 'Z':
        case 'a' ... 'z':
            return Prev(), HandleIdentifier(beginLoc);
        case '"':
            return HandleString(beginLoc);

        case -1:
            return new EndToken(beginLoc);
        default:
            ErrAt(beginLoc, "unexpected character '%c'", c);
            return nullptr;
    }
}

std::list<BaseToken*>* Lexer::Scan() {
    auto tokens = new std::list<BaseToken*>();
    for (BaseToken* tok; (tok = NextToken())->tag != Tag::End;) {
        tokens->push_back(tok);
    }
    return tokens;
}

void* Lexer::Run(void* input) {
    text = *(std::string*)input;
    it = text.begin();
    auto* ret = Scan();
    return ret;
}
